/**
 * Copyright (c) 2019-2029, wwww.kiwipeach.cn (liuburu@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * https://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cn.kiwipeach.blog.exception;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;
import org.springframework.web.context.request.WebRequest;

import java.util.*;

/**
 * 自定义异常输出，能够自适应客户端进行视图数据和报文数据返回
 *
 * @author kiwipeach
 * @since 1.0
 */
@Component
@Slf4j
public class CustomErrorAttributes extends DefaultErrorAttributes {

    public CustomErrorAttributes() {
    }

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        //FIXME 堆栈信息(errorAttributes.get("trace"))不显示给前端，做后台记录
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
        Throwable error = super.getError(webRequest);
        return customErrorException(errorAttributes, error);
    }

    /**
     * 自定义状态码
     *
     * @param errorAttributes 错误属性
     * @param error           异常
     * @return 返回状态码
     */
    private Map<String, Object> customErrorException(Map<String, Object> errorAttributes, Throwable error) {
        Map<String, Object> resultMap = new HashMap<>(2);
        JSONObject data = new JSONObject();
        Integer status = (Integer) errorAttributes.get("status");
        if (error instanceof BlogException) {
            String code = ((BlogException) error).getCode();
            // 登录认证异常
            if (code.contains("LOGIN")) {
                status = 401;
            }
            data.put("error", null);
            resultMap.put("message", error.getMessage());
        } else if (error instanceof UnauthenticatedException) {
            status = 401;
            resultMap.put("message", "未检测到登录用户信息，请先登录");
        } else if (error instanceof UnauthorizedException) {
            status = 403;
            resultMap.put("message", "当前用户没有访问权限");
        } else {
            resultMap.put("message", errorAttributes.get("message"));
        }
        resultMap.put("code", status);
        data.put("error", errorAttributes.get("error"));
        data.put("path", errorAttributes.get("path"));
        data.put("errorDetails", getErrorListMsg(errorAttributes.get("errors")));
        //data.put("stack", getHumanExceptionStack(error));
        resultMap.put("time", errorAttributes.get("timestamp"));
        resultMap.put("data", data);
        return resultMap;
    }

    /**
     * 获取可读性高的异常信息
     *
     * @param e 异常对象
     * @return 返回异常对象堆栈
     */
    private LinkedList<StackTraceElement> getHumanExceptionStack(Throwable e) {
        if (e == null) {
            return null;
        }
        StackTraceElement[] stackTrace = e.getStackTrace();
        LinkedList<StackTraceElement> resultStack = new LinkedList<>();
        for (StackTraceElement element : stackTrace) {
            resultStack.add(element);
        }
        return resultStack;
    }

    private List<String> getErrorListMsg(Object errors) {
        List<String> errorResult = new ArrayList<>();
        if (errors instanceof Collection) {
            Collection errorsList = (Collection) errors;
            Iterator iterator = errorsList.iterator();
            while (iterator.hasNext()) {
                Object next = iterator.next();
                if (next instanceof FieldError) {
                    String defaultErrorMsg = ((FieldError) next).getDefaultMessage();
                    errorResult.add(defaultErrorMsg);
                }
            }
        }
        return errorResult;
    }

}
